﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CSharpExperiments
{
	public static class Try
	{
		public static T Catch<T>(this Func<T> tryFunc, T defaultValue)
		{
			try { return tryFunc(); } catch { return defaultValue; }
		}

		public static T Catch<T>(this Func<T> tryFunc, Func<T> catchFunc)
		{
			try { return tryFunc(); } catch { return catchFunc(); }
		}

		public static T Catch<T>(this Func<T> tryFunc, Func<Exception, T> catchFunc)
		{
			try { return tryFunc(); } catch (Exception thrown) { return catchFunc(thrown); }
		}

		public static Func<T> WithCatch<T>(this Func<T> tryFunc, T defaultValue)
		{
			return () => { try { return tryFunc(); } catch { return defaultValue; } };
		}

		public static Func<T> WithCatch<T>(this Func<T> tryFunc, Func<T> catchFunc)
		{
			return () => { try { return tryFunc(); } catch { return catchFunc(); } };
		}

		public static Func<T> WithCatch<T>(this Func<T> tryFunc, Func<Exception, T> catchFunc)
		{
			return () => { try { return tryFunc(); } catch (Exception thrown) { return catchFunc(thrown); } };
		}

		public static T Catch<T, E>(this Func<T> tryFunc, T defaultValue) where E : Exception
		{
			try { return tryFunc(); } catch (E) { return defaultValue; }
		}

		public static T Catch<T, E>(this Func<T> tryFunc, Func<T> catchFunc) where E : Exception
		{
			try { return tryFunc(); } catch (E) { return catchFunc(); }
		}

		public static T Catch<T, E>(this Func<T> tryFunc, Func<E, T> catchFunc) where E : Exception
		{
			try { return tryFunc(); } catch (E exc) { return catchFunc(exc); }
		}

		public static Func<T> WithCatch<T, E>(this Func<T> tryFunc, T defaultValue) where E : Exception
		{
			return () => { try { return tryFunc(); } catch (E) { return defaultValue; } };
		}

		public static Func<T> WithCatch<T, E>(this Func<T> tryFunc, Func<T> catchFunc) where E : Exception
		{
			return () => { try { return tryFunc(); } catch (E) { return catchFunc(); } };
		}

		public static Func<T> WithCatch<T, E>(this Func<T> tryFunc, Func<E, T> catchFunc) where E : Exception
		{
			return () => { try { return tryFunc(); } catch (E exc) { return catchFunc(exc); } };
		}

		public static E Expect<E>(Action todo) where E : Exception
		{
			try { todo(); } catch (E e) { return e; } throw new ApplicationException("Expected " + typeof(E).Name);
		}

		public static Exception Expect(Action todo)
		{
			return Expect<Exception>(todo);
		}

		public static E CatchAndReturn<E>(Action todo) where E : Exception
		{
			try { todo(); return null; } catch (E e) { return e; }
		}

		public static Exception CatchAndReturn(Action todo)
		{
			return CatchAndReturn<Exception>(todo);
		}

		public static bool DidThrow<E>(Action todo) where E : Exception
		{
			try { todo(); return false; } catch (E) { return true; }
		}

		public static bool DidThrow(Action todo)
		{
			return DidThrow<Exception>(todo);
		}

		// TODO: maybe we need alternate IEnumerable<T> called IEnumerableExceptionalable<T, E>
		// which can return the exceptions as well as the return values?
		// could have a method like ThrowFirst() or ThrowLast()

		public static IEnumerable<Exception> Many<T>(IEnumerable<T> values, Action<T> todo)
		{
			return values.Select(x => CatchAndReturn(() => todo(x)));
		}
	}
}
